package LAAMI;

import java.io.*;
import java.math.BigDecimal;
import java.util.*;

import cern.colt.list.DoubleArrayList;
import cern.jet.stat.Descriptive;
import repast.simphony.engine.environment.RunEnvironment;
import repast.simphony.engine.schedule.ScheduledMethod;

public class OptionMarket {

	// Option market price
	private double price;
	private double strikePrice;
	private double maxCall;
	private double minAsk;
	// Black-Scholes model prices (theoretical prices)
	private double BSprice;
	// Pricing bias or pricing error, which is defined as market
	// price/theoretical price
	private double pricingBias;
	// Arraylist with all pricing bias from day 51 until the expiration
	// date of the options.
	private double maturity;
	private String pjpFileName;

	public OptionMarket(double optionPrice, double maturity) {
		setPrice(0);
		setMaturity(maturity);
		setStrikePrice(0);
		setPJPfn();
	}

	private void setPJPfn() {
		BigDecimal sigmadb = new BigDecimal(LAAMIBuilder.getStockVolatility());
		BigDecimal mudb = new BigDecimal(LAAMIBuilder.getDrift());
		sigmadb=sigmadb.setScale(1,BigDecimal.ROUND_FLOOR);
		mudb=mudb.setScale(1, BigDecimal.ROUND_DOWN);
		pjpFileName = "output/pjp_" + sigmadb.toString() + "_"
				+ mudb.toString() + ".txt";
	}

	// In every step, it's the last method to be executed.
	// At day 51, when the option market starts operating, we generate a
	// fixed strike price. It is equal to a uniform random number in 0.9 to 1.1
	// multiplied by the current price of the underlying stock market.
	// After day 50, in each round we find the maximum call bid and the minimum
	// ask quote out of all the agents. We calculate the option price,
	// the market price according to B-S model and the pricing bias.
	// We maintain a list of all the pricing bias of the trading period.
	// When the day of expiration comes we calculate the mean, max, min,
	// standard deviation, Kurtosis, skewness of all recorded the pricing bias.
	@ScheduledMethod(start = 1, interval = 1, priority = 3)
	public void step() {
		int curDay = (int) RunEnvironment.getInstance().getCurrentSchedule()
				.getTickCount();
		if (curDay == 50) {
			Random g = new Random();
			double r = g.nextDouble() * 0.2 + 0.9;
			double stockPrice = LAAMIBuilder.getStockmarket().getPrice();
			setStrikePrice(stockPrice * r);
		}

		if (curDay > 50 && curDay < LAAMIBuilder.getExpDay()) {
			setMaxCall(findMaxCall());
			setMinAsk(findMinAsk());
			setPrice(calcOptionPrice());
		}

		// I should check "-1"
		if (curDay == LAAMIBuilder.getExpDay() - 1) {
			setBSprice(calculateBSprice());
			setPricingBias(calcPb());

			// printPJ(findWinner(), winnerFileName);
			// printPJ(findLoser(), loserFileName);
			printPJPAver(pjpFileName);
		}

	}

	// If the maximum call price is at least as big as the minimum ask price
	// out of all the agents, then option market price is equal to the average
	// of the maximum call and the minimum ask. Otherwise, it stays the same.
	public double calcOptionPrice() {
		double op = getPrice();
		if (maxCall >= minAsk)
			op = (double) (maxCall + minAsk) / 2;
		else
			System.out.println("No trading.");
		return op;
	}

	// We calculate the option market price using Black-Scholes model
	// with the "true" volatility. The other parameters of B-S model are:
	// the current price of the underlying stock market, the strike price,
	// the drift, the interest rate and the maturity.
	public double calculateBSprice() {
		double trueVolatil = LAAMIBuilder.getStockVolatility();
		return BlackScholes.getPrice(trueVolatil);
	}

	// In every step, calculate the pricing bias as the fraction of the
	// market price over the price produced from the BS model with "known"
	// volatility. Then, add the pricing bias of the day in pbList
	public double calcPb() {
		if (BSprice == 0)
			BSprice = Double.MIN_VALUE;
		double result = Math.log(price / BSprice);
		try {
			if (Double.isNaN(result)) {
				throw new Exception("NaN Pricing Bias");
			}
			if (Double.isInfinite(result)) {
				throw new Exception("Infinite Pricing Bias");
			}
		} catch (Exception ex) {
			return 0;
		}
		return result;
	}

	public int findWinner() {
		double maxPr = Double.MIN_VALUE;
		int win = 0;
		for (int i = 0; i < LAAMIBuilder.getInvestorCount(); i++) {
			double pr = LAAMIBuilder.getAgentList().get(i).getProfit();
			if (pr > maxPr) {
				win = i;
				maxPr = pr;
			}
		}
		return win;
	}

	public int findLoser() {
		double minPr = Double.MAX_VALUE;
		int loser = 0;
		for (int i = 0; i < LAAMIBuilder.getInvestorCount(); i++) {
			double pr = LAAMIBuilder.getAgentList().get(i).getProfit();
			if (pr < minPr) {
				loser = i;
				minPr = pr;
			}
		}
		return loser;
	}

	public void printPJ(int agent, String filename) {
		try {
			int p = (int) LAAMIBuilder.getAgentList().get(agent).getPatience();
			double j = (double) LAAMIBuilder.getAgentList().get(agent)
					.getJudgement();
			FileWriter fstream = new FileWriter(filename, true);
			BufferedWriter out = new BufferedWriter(fstream);
			out.write(String.valueOf(p) + "," + String.valueOf(j) + ",\n");
			out.close();
		} catch (IOException e) {
		}
	}

	public void printPJPAver(String filename) {
		for (int p = 5; p < 45; p += 4) {
			for (double j = 0.5; j < 1.5; j += 0.1) {
				DoubleArrayList a = new DoubleArrayList();
				for (int i = 0; i < LAAMIBuilder.getInvestorCount(); i++) {
					Set set = new Set(LAAMIBuilder.getAgentList().get(i)
							.getPatience(), LAAMIBuilder.getAgentList().get(i)
							.getJudgement(), LAAMIBuilder.getAgentList().get(i)
							.getProfit());
					if (set.getPat() >= p && set.getPat() < p + 4
							&& set.getJud() >= j && set.getJud() < j + 0.1)
						a.add(set.getProf());
				}
				Set averSet = new Set();
				averSet.setJud(j + 0.05);
				averSet.setPat(p + 2);
				if (Double.isNaN(Descriptive.mean(a))
						|| Double.isInfinite(Descriptive.mean(a)))
					averSet.setProf(0);
				else
					averSet.setProf(Descriptive.mean(a));
				print(averSet, pjpFileName);
			}
		}
	}

	// Out of all the investors find the maximum call price
	public double findMaxCall() {
		double max = Double.MIN_VALUE;
		Iterator<Investor> iterator = LAAMIBuilder.getAgentList().iterator();
		double callPrice;
		while (iterator.hasNext()) {
			callPrice = iterator.next().getCallPrice();
			if (callPrice > max)
				max = callPrice;
		}
		return max;
	}

	// Out of all the investors find the minimum ask price
	public double findMinAsk() {
		double min = Double.MAX_VALUE;
		Iterator<Investor> iterator = LAAMIBuilder.getAgentList().iterator();
		double askPrice;
		while (iterator.hasNext()) {
			askPrice = iterator.next().getAskPrice();
			if (askPrice < min)
				min = askPrice;
		}
		return min;
	}

	public static void print(Set a, String filename) {
		try {
			FileWriter fstream = new FileWriter(filename, true);
			BufferedWriter out = new BufferedWriter(fstream);
			String s = a.getPat() + " " + a.getJud() + " " + a.getProf() + "\n";
			out.write(s);
			out.close();
		} catch (IOException e) {
		}
	}

	/* -------------setters/getters-------------------- */
	public double getMaxCall() {
		return maxCall;
	}

	public void setMaxCall(double m) {
		maxCall = m;
	}

	public double getMinAsk() {
		return minAsk;
	}

	public void setMinAsk(double m) {
		minAsk = m;
	}

	public double getPrice() {
		return price;
	}

	public void setPrice(double p) {
		price = p;
	}

	public void setBSprice(double s) {
		BSprice = s;
	}

	public double getBSprice() {
		return BSprice;
	}

	public double getPricingBias() {
		return pricingBias;
	}

	public void setPricingBias(double p) {
		pricingBias = p;
	}

	public double getMaturity() {
		return maturity;
	}

	public void setMaturity(double maturity) {
		this.maturity = maturity;
	}

	public double getStrikePrice() {
		return strikePrice;
	}

	public void setStrikePrice(double s) {
		strikePrice = s;
	}
}
